package com.yuxiao.dynmic.scheduled.job;

import com.alibaba.nacos.spring.context.event.config.NacosConfigReceivedEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.ScheduledTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.util.StringValueResolver;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 动态任务管理
 *
 * @author yangjunwei
 * @date 2022-01-25 22:12
 */
@Slf4j
@EnableScheduling
@Configuration
public class DynamicJobManager implements SchedulingConfigurer, EmbeddedValueResolverAware, ApplicationListener<NacosConfigReceivedEvent> {

    /**
     * 暂存已注册的定时任务
     */
    private ConcurrentHashMap<String, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>();

    /**
     *
     */
    private ConcurrentHashMap<String, Runnable> waitRegisterTask = new ConcurrentHashMap();

    /**
     * 任务调度记录
     */
    private ConcurrentHashMap<String, String> taskCron = new ConcurrentHashMap<>();

    /**
     * 定时任务注册器
     */
    private ScheduledTaskRegistrar taskRegistrar;


    /**
     * 占位符属性解析器
     */
    private StringValueResolver stringValueResolver;


    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        this.taskRegistrar = taskRegistrar;
        Set<Map.Entry<String, Runnable>> entries = waitRegisterTask.entrySet();
        for (Map.Entry<String, Runnable> next : entries) {
            String key = next.getKey();
            String cronVal = taskCron.get(key);
            register(key, cronVal, next.getValue());
        }
    }


    /**
     * Nacos配置修改监听
     * @param event 配置变更接受事件
     */
    @Override
    public void onApplicationEvent(NacosConfigReceivedEvent event) {
        Set<Map.Entry<String, String>> entries = taskCron.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            String key = entry.getKey();
            String cron = parseCronKey(key);
            String oldCronVal = entry.getValue();
            String newCronVal = stringValueResolver.resolveStringValue(cron);
            if(!oldCronVal.equals(newCronVal)) {
                register(key, newCronVal, scheduledTasks.get(key).getTask().getRunnable());
                log.info("Rescheduled task:[{}] from [{}] to [{}]", parseTaskName(key), oldCronVal, newCronVal);
            }
        }
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.stringValueResolver = resolver;
    }


    /**
     * 注册定时任务
     *
     * @param taskName 任务名称
     * @param cron     任务调度周期
     * @param runnable 任务
     * @return
     */
    public boolean register(String taskName, String cron, Runnable runnable) {
        if(taskRegistrar != null) {
            CronTask cronTask = new CronTask(runnable, cron);
            ScheduledTask scheduledTask = taskRegistrar.scheduleCronTask(cronTask);
            ScheduledTask oldTask = scheduledTasks.put(taskName, scheduledTask);
            if(oldTask != null) {
                oldTask.cancel();
            } else {
                log.info("Scheduled task {} cron [{}]", taskName, cron);
            }
        } else {
            waitRegisterTask.put(taskName, runnable);
        }
        taskCron.put(taskName, cron);
        return true;
    }


    /**
     * 重新编排任务，任务不存在，不做任何处理，任务存在，则取消已有的任务，重新设置cron
     *
     * @param taskName 任务名称
     * @param cron     新的任务调度周期
     * @return
     */
    public boolean reScheduler(String taskName, String cron) {
        //todo: 任务重编排逻辑
        return true;
    }

    private String parseTaskName(String key) {
        return key.split(":")[0];
    }


    private String parseCronKey(String key) {
        return key.split(":")[1];
    }


}
